Имеется набор
целых чисел a1, a2,
..., an. Определите
количество чисел в диапазоне от l до r включительно, которые делятся
хотя бы на одно число из данного набора.
Вход. Состоит из нескольких тестов.
Первая строка каждого теста содержит два целых числа l (1 ≤ l ≤ 109) и r (1 ≤ r ≤ 109), задающих границы диапазона. Вторая
строка содержит количество элементов n
(1 ≤ n ≤ 18) в
наборе и сам набор чисел
a1, a2, ..., an . Каждое число в наборе лежит в пределах
от 1 до 109.
Выход. Для каждого теста в отдельной
строке выведите количество чисел из диапазона [l; r], которые
делятся хотя бы на одно число из набора a1, a2, ..., an.
Пример
входа |
Пример
выхода |
293 784 1 1 579000
987654 2 1 2 1 1000000000 2 2 3 |
492 408655 666666667 |
РЕШЕНИЕ
комбинаторика - принцип включения-исключения
Пусть a = { a1, a2,
..., an } – множество чисел.
Пусть numDivisible(l, r, a) – количество чисел от l до r
включительно, которые делятся хотя бы на одно из чисел a1, a2,
..., an. Воспользуемся
фактом, что
numDivisible(l, r, a) = numDivisible(1, r, a) –
numDivisible(1, l – 1, a).
Значение numDivisible(1, n, a) будем вычислять при
помощи функции f(n, a). Рассмотрим процесс вычисления
результата в зависимости от количества элементов в массиве a.
1. Если a содержит один элемент, то ответом
будет значение n / a[0] (округление до целого производится
вниз).
2. Пусть a содержит два элемента. Ответ будет
меньше n / a[0] + n / a[1], так как будут существовать числа,
которые делятся на a[0] и на a[1] одновременно. И в приведенной сумме
такие числа будут считаться дважды. Количество чисел, делящихся одновременно на
a[0] и на a[1], равно n / НОК(a[0], a[1]). Таким образом, для двух чисел
f(n, a)
= n / a[0] + n / a[1] –
n / НОК(a[0], a[1])
3. Пусть a содержит три элемента. Тогда согласно
принципу включения – исключения
f(n, a)
= n / a[0] + n / a[1] + n
/ a[2] –
– n / НОК(a[0], a[1]) – n /
НОК(a[1], a[2]) – n / НОК(a[0], a[2]) +
n / НОК(a[0], a[1], a[2])
Поскольку по
условию задачи массив a содержит от 1
до 18 элементов, то перебрать все подмножества множества a можно не более чем за 218 операций. При этом если
подмножество содержит нечетное
число элементов, то значение n / НОК() следует прибавлять к накапливаемой сумме (ответу), если
четное – то вычитать.
Если при
вычислении НОК() текущее значение НОК() станет больше n
для некоторого j < k, то процесс вычисления НОК() можно завершить, так как тогда n / НОК() = 0.
Реализация алгоритма
Функция f(n, a)
вычисляет значение numDivisible(1, n, a). Функция gcd вычисляет наибольший
общий делитель двух чисел.
int f(int N, int *a)
{
Ответ вычисляем в переменной res.
int res = 0;
Перебираем все подмножества множества { a1, a2, ..., an }. Переменная i содержит маску
подмножеств.
for(int i = 1; i <
(1<<n); i++)
{
В переменной lcm вычисляем НОК
подмножества, задаваемого маской i.
long long
lcm = 1;
В переменной bits подсчитываем
количество элементов в подмножестве, задаваемой маской i (число бит, равных 1, в i).
int bits = 0;
for(int j
= 0; j < n; j++)
if (i & (1 << j))
{
bits++;
int temp = gcd(lcm,a[j]);
lcm = lcm / temp * a[j];
Если НОК подмножества становится больше N, то далее
вычилять нет смысла. Все равно
значение N / lcm равно нулю.
if (lcm > N) break;
}
В зависимости от четности числа битов в маске (количества
элементов в текущем рассматриваемом подмножестве), прибавляем или вычитаем
очередное слагаемое.
if (bits % 2) res += N / lcm; else res -= N / lcm;
}
return res;
}
Основная часть
программы. Читаем входные данные.
while(scanf("%d %d",&l,&r)
== 2)
{
scanf("%d",&n);
for(i = 0; i < n; i++) scanf("%d",&a[i]);
Вычисляем искомое количество чисел на отрезке [l; r ].
res = f(r,a) - f(l - 1,a);
Выводим ответ.
printf("%d\n",res);
}